/*
**************************************************************************************
*Myriad Engine                                                                       *
*Copyright (C) 2006-2007, 5d Studios (www.5d-Studios.com)                            *
*                                                                                    *
*This library is free software; you can redistribute it and/or                       *
*modify it under the terms of the GNU Lesser General Public                          *
*License as published by the Free Software Foundation; either                        *
*version 2.1 of the License, or (at your option) any later version.                  *
*                                                                                    *
*This library is distributed in the hope that it will be useful,                     *
*but WITHOUT ANY WARRANTY; without even the implied warranty of                      *
*MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU                   *
*Lesser General Public License for more details.                                     *
*                                                                                    *
*You should have received a copy of the GNU Lesser General Public                    *
*License along with this library; if not, write to the Free Software                 *
*Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA      *
**************************************************************************************
*/
package leola.frontend.parsers;

import static leola.ast.ASTAttributes.LINE;

import java.util.EnumSet;

import leola.ast.ASTNode;
import leola.ast.BreakStmt;
import leola.ast.ContinueStmt;
import leola.ast.EmptyStmt;
import leola.ast.Stmt;
import leola.frontend.EofToken;
import leola.frontend.LeolaParser;
import leola.frontend.Token;
import leola.frontend.tokens.LeolaErrorCode;
import leola.frontend.tokens.LeolaTokenType;
import static leola.frontend.tokens.LeolaTokenType.*;

/**
 * Parsers a {@link Stmt}.
 * 
 * @author Tony
 *
 */
public class StmtParser extends LeolaParser {

    // Synchronization set for starting a statement.
    protected static final EnumSet<LeolaTokenType> STMT_START_SET =
        EnumSet.of(FOR, IF, VAR, VAL, WHILE, LEFT_BRACE, 
        		   RETURN, BREAK, CONTINUE, IDENTIFIER, SEMICOLON, CLASS);

    // Synchronization set for following a statement.
    protected static final EnumSet<LeolaTokenType> STMT_FOLLOW_SET =
        EnumSet.of(SEMICOLON, RIGHT_BRACE, ELSE, DOT);
	
	/**
	 * @param parser
	 */
	public StmtParser(LeolaParser parser) {
		super(parser);
	}
	
	/**
     * Parse a statement.
     * To be overridden by the specialized statement parser subclasses.
     * @param token the initial token.
     * @return the root node of the generated parse tree.
     * @throws Exception if an error occurred.
     */
    public ASTNode parse(Token token)
        throws Exception
    {
        ASTNode statementNode = null;

        LeolaTokenType type = token.getType();
        switch (type) {
        	case LEFT_BRACE: {
        		CompoundStmtParser parser = new CompoundStmtParser(this);
        		statementNode = parser.parse(token);
        		break;
        	}
        	case VAR: {
        		VarDeclStmtParser parser = new VarDeclStmtParser(this);
        		statementNode = parser.parse(token);
        		
        		eatOptionalStmtEnd(currentToken());
        		break;
        	}
        	case VAL: {
        		VarDeclStmtParser parser = new VarDeclStmtParser(this, true);
        		statementNode = parser.parse(token);
        		
        		eatOptionalStmtEnd(currentToken());
        		break;
        	}
        	case NEW:
        	case IDENTIFIER: {
        		ExprParser parser = new ExprParser(this);
        		statementNode = parser.parse(token);
        		
        		eatOptionalStmtEnd(currentToken());
        		break;
        	}
        	case IF: {
        		IfStmtParser parser = new IfStmtParser(this);
        		statementNode = parser.parse(token);
        		break;
        	}
        	case WHILE: {
        		WhileStmtParser parser = new WhileStmtParser(this);
        		statementNode = parser.parse(token);
        		break;
        	}
        	case RETURN: {
        		ReturnStmtParser parser = new ReturnStmtParser(this);
        		statementNode = parser.parse(token);
        		
        		eatOptionalStmtEnd(currentToken());
        		break;
        	}
        	case BREAK: {
        		statementNode = new BreakStmt();
        		eatOptionalStmtEnd(nextToken());
        		break;
        	}
        	case CONTINUE: {
        		statementNode = new ContinueStmt();
        		eatOptionalStmtEnd(nextToken());
        		break;
        	}        	
        	case CLASS: {
        		ClassDefStmtParser parser = new ClassDefStmtParser(this);
        		statementNode = parser.parse(token);
        		break;
        	}
            default: {
                statementNode = new EmptyStmt();
                eatOptionalStmtEnd(currentToken());
                break;
            }
        }

        // Set the current line number as an attribute.
        setLineNumber(statementNode, token);

        return statementNode;
    }

    /**
     * Eats the optional line end
     * @param token
     * @return
     * @throws Exception
     */
    protected Token eatOptionalStmtEnd(Token token) throws Exception {
    	LeolaTokenType type = token.getType();
    	if ( type.equals(LeolaTokenType.SEMICOLON) ) {
    		token = nextToken();
    	}
    	
    	return token;
    }
    
    /**
     * Parse a statement list.
     * @param token the curent token.
     * @param parentNode the parent node of the statement list.
     * @param terminator the token type of the node that terminates the list.
     * @param errorCode the error code if the terminator token is missing.
     * @throws Exception if an error occurred.
     */
    protected void parseList(Token token, ASTNode parentNode,
                             LeolaTokenType terminator,
                             LeolaErrorCode errorCode)
        throws Exception
    {
        // Synchronization set for the terminator.
        EnumSet<LeolaTokenType> terminatorSet = STMT_START_SET.clone();
        terminatorSet.add(terminator);

        // Loop to parse each statement until the END token
        // or the end of the source file.
        while (!(token instanceof EofToken) &&
               (token.getType() != terminator)) {

            // Parse a statement.  The parent node adopts the statement node.
            ASTNode statementNode = parse(token);
            parentNode.addChild(statementNode);

            token = currentToken();
            LeolaTokenType tokenType = token.getType();

            // Look for the semicolon between statements.
            if (tokenType == SEMICOLON) {
                token = nextToken();  // consume the ;
            }

            // If at the start of the next statement, then missing a semicolon.
//            else if (STMT_START_SET.contains(tokenType)) {
//                getExceptionHandler().errorToken(token, this, LeolaErrorCode.MISSING_SEMICOLON);
//            }

            // Synchronize at the start of the next statement
            // or at the terminator.
            token = synchronize(terminatorSet);
        }

        // Look for the terminator token.
        if (token.getType() == terminator) {
            token = nextToken();  // consume the terminator token
        }
        else {            
            getExceptionHandler().errorToken(token, this, errorCode);
        }
    }
    
    /**
     * Set the current line number as a statement node attribute.
     * @param node ICodeNode
     * @param token Token
     */
    protected void setLineNumber(ASTNode node, Token token) {
        if (node != null) {
            node.setAttribute(LINE, token.getLineNumber());
        }
    }   

    /**
     * Synchronize the parser.
     * @param syncSet the set of token types for synchronizing the parser.
     * @return the token where the parser has synchronized.
     * @throws Exception if an error occurred.
     */
    public Token synchronize(EnumSet<LeolaTokenType> syncSet)
        throws Exception
    {
        Token token = currentToken();

        // If the current token is not in the synchronization set,
        // then it is unexpected and the parser must recover.
        if (!syncSet.contains(token.getType())) {

            // Flag the unexpected token.            
            getExceptionHandler().errorToken(token, this, LeolaErrorCode.UNEXPECTED_TOKEN);

            // Recover by skipping tokens that are not
            // in the synchronization set.
            do {
                token = nextToken();
            } while (!(token instanceof EofToken) &&
                     !syncSet.contains(token.getType()));
       }

       return token;
    }
}
